package com.example.adminbase.shiro;

import com.example.adminbase.dao.SysPermMapper;
import com.example.adminbase.dao.SysRoleMapper;
import com.example.adminbase.dao.SysUserMapper;
import com.example.adminbase.domain.*;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.SimpleByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;

import java.time.format.DateTimeFormatter;
import java.util.stream.Collectors;

public class DefaultAuthorizingRealm extends AuthorizingRealm {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysPermMapper sysPermMapper;

    @Autowired
    private SysRoleMapper sysRoleMapper;

    @Autowired
    private MessageSource messageSource;

    public DefaultAuthorizingRealm() {
        setCredentialsMatcher(new HashedCredentialsMatcher(Md5Hash.ALGORITHM_NAME));
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        authorizationInfo.setStringPermissions(sysPermMapper.selectPermFromUserName(userName).stream()
                .map(SysPerm::getPermission).collect(Collectors.toSet()));
        authorizationInfo.setRoles(sysRoleMapper.selectRoleFromUserName(userName).stream().map(SysRole::getRoleKey)
                .collect(Collectors.toSet()));

        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername(),
                password = new String(usernamePasswordToken.getPassword());

        SysUser user = sysUserMapper.findByUsername(username);
        AuthenticationException e = null;
        if(user == null) {
            e = new UnknownAccountException(messageSource.getMessage("user.notfound", null, null));
        }else {
            //todo: 因为账户的锁定状态是可能随时间变化的，所以每次查看用户状态前需要先刷新用户状态。修改SysUser类，在查看前自行刷新用户状态。
            user.decideLockState();
            if(user.getLockState() == 1 && !user.validPassword(password) ) {    //已锁定的用户不用再验证密码
                user.increasePasswordErrorTimes();
                e = new IncorrectCredentialsException(messageSource.getMessage("user.incorrect_password",
                        new Integer[]{
                                user.getMaxTryTimes() - user.getPswdErrorTimes()
                        }, null));
            }
            if(user.getLockState() == 0) {
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constant.DATE_MINUTES_FORMAT);
                e = new LockedAccountException(messageSource.getMessage("user.locked",
                        new String[]{
                                dateTimeFormatter.format(user.lockEndTime())
                        }, null));
            }
            sysUserMapper.updateSelective(user);
        }
        if(e != null) {
            throw e;
        }else {
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
            simpleAuthenticationInfo.setCredentialsSalt(new SimpleByteSource(EncryptUtil.PASSWORD_SALT));
            SecurityUtils.getSubject().getSession().setAttribute("user", user);
            return simpleAuthenticationInfo;
        }
    }

    public static void main(String[] args) {
        System.out.println(EncryptUtil.encryptPassword("123"));
        System.out.println(EncryptUtil.encryptPassword("123"));
    }
}
